module material.ashimaterial;

import material.material;
import material.texture;
import render.render;
import scene.scene;
import scene.ray;
import scene.intersection;
import shader.light;
import utils.color;
import utils.vector3;

import std.math;

import std.stdio;

public class AshiMaterial : Material {
    private float x_1;
    private float mN_u, mN_v;
    private Texture mR_s;
    private Texture mR_d;

    const NEARZERO = 1e-8f;
    const NEARONE = 0.99f;

    public final const uint TEXTURE_RS = 0;
    public final const uint TEXTURE_RD = 1;

    public void setTexture(uint index, Texture tex) {
        switch (index) {
            case TEXTURE_RS: mR_s = tex; break;
            case TEXTURE_RD: mR_d = tex; break;
            default:
        }
    }

    public Texture getTexture(uint index) {
        switch (index) {
            case TEXTURE_RS: return mR_s; break;
            case TEXTURE_RD: return mR_d; break;
            default: return null;
        }
    }

    public this() {
        setParams(20, 20, RGBColor(0.5f, 0.5f, 0.5f), RGBColor(0.5f, 0.5f, 0.5f));
    }

    private float sqr(float x) {
        return x*x;
    }

    public RGBColor value(inout RenderContext context, Vector3f light_vector) {
        auto R_s = mR_s.getColor(context);
        auto R_d = mR_d.getColor(context);

        void calcFactors(inout float diffuse, inout RGBColor specular) {
            static float energyConservation = 28.0 / (23.0 * PI);

            Vector3f k1 = light_vector.normal();
            Vector3f k2 = -context.ray.dir.normal();
            Vector3f k = k2;
            Vector3f h = (k1+k2).normal();
            Vector3f n = context.rinters.normal;

            diffuse = 0;
            specular = RGBColor(0);

            //xxx: find 2 arbitrary orthogonal vectors to form an orthogonal base
            Vector3f fool = Vector3f(n.y, n.z, n.x);
            Vector3f u = fool | n;
            Vector3f v = u | n;

            float ndotk2 = n*k2;
            if (ndotk2 < NEARZERO)
                return;

            float ndotk1 = n*k1;
            if (ndotk1 < NEARZERO)
                ndotk1 = 0;

            float fromk1 = (1.0f-pow(1.0f-(ndotk1)*0.5f, 5.0f));
            float fromk2 = (1.0f-pow(1.0f-(ndotk2)*0.5f, 5.0f));
            diffuse = energyConservation*fromk1*fromk2;

            float hdotn = h*n;
            //if (hdotn>=NEARONE)
            //    hdotn = NEARONE;
            //if (hdotn < 0)
            //    hdotn = 0;
            float hdotu = h*u;
            float hdotv = h*v;
            float hdotk = h*k;

            RGBColor fresnel = R_s + (RGBColor(1.0f)-R_s)*pow(1.0f-(hdotk), 5.0f);
            float nuNvFactor = sqrt((mN_u+1.0f)*(mN_v+1.0f));
            float rhoSconst = nuNvFactor / (8.0f * PI);

            float exponent = (( mN_u*hdotu*hdotu ) + ( mN_v*hdotv*hdotv )) / (1 - (hdotn*hdotn));
            //if (exponent>mN_u+mN_v)
            //    exponent = mN_u+mN_v;

            float num = pow(hdotn, exponent);
            if (!(num==num)) {
                std.stdio.writefln("%s %s",hdotn,exponent);
                assert(false);
            }
            float den = hdotk * fmax(ndotk1, ndotk2);

            specular = rhoSconst*(num/den)*fresnel;
        }

        float diffuse;
        RGBColor specular;
        calcFactors(diffuse,specular);

        /*float tmp1 = x_1*pow(n*h, (mN_u*sqr(h*u)+mN_v*sqr(h*v))/(1.0f-sqr(h*n)));
        float tmp2 = tmp1 / ((h*k)*fmax(n*k1, n*k2));
        RGBColor ps_s = F_dings*tmp2;

        float tmp3 = (1-pow(1-(n*k1)/2.0f, 5.0f))*(1-pow(1-(n*k1)/2.0f, 5.0f));
        RGBColor p_d = R_d*energyConservation*(RGBColor(1.0f) - R_s)*tmp3;*/

        //writefln((ps_s + p_d));

        assert(diffuse==diffuse);
        assert(specular.r == specular.r);
        assert(specular.g == specular.g);
        assert(specular.b == specular.b);

        return diffuse*R_d*(RGBColor(1.0f) - R_s) + specular;
    }

    public void setParams(float n_u, float n_v, RGBColor R_s, RGBColor R_d) {
        x_1 = sqrt((n_u+1)*(n_v+1))/(8*PI);
        mN_u = n_u;
        mN_v = n_v;
        mR_s = new Texture(R_s);
        mR_d = new Texture(R_d);
    }

    public char[] toString() {
        return "BRDF";
    }

}
